﻿using MongoDB.Driver;
using System.Reflection;
using Testudo.Core;

namespace Testudo.Mongo;

internal static class UpdateWithValueExtensions
{
    internal static List<FieldChangeRecord> GetFields<TEntity>(this TEntity source)
    {
        var props = typeof(TEntity).GetProperties(BindingFlags.Public | BindingFlags.Instance |
                                                  BindingFlags.DeclaredOnly);

        return props.Select(a => new FieldChangeRecord
        {
            Field = a.Name,
            NewValue = a.GetValue(source)
        }).Where(a => a.NewValue != null).ToList();
    }

    internal static (List<FieldChangeRecord>, UpdateDefinition<TEntity>) Slash<TEntity>(this TEntity source,
        SlashData slash)
        where TEntity : class, IEntity
    {
        List<FieldChangeRecord> records = new();
        var props = typeof(TEntity).GetProperties(BindingFlags.Public | BindingFlags.Instance |
                                                  BindingFlags.DeclaredOnly);
        foreach (var item in slash)
        {
            var prop = props.FirstOrDefault(a => a.Name == item);
            if (prop != null)
            {
                var oldValue = prop.GetValue(source);

                records.Add(new FieldChangeRecord { Field = prop.Name, OldValue = oldValue, NewValue = default });
            }
        }

        var updateTarget = Builders<TEntity>.Update.Set(a => a.Id, source.Id);
        foreach (var item in records) updateTarget.Unset(item.Field);
        updateTarget = updateTarget.Set(nameof(IEntity.UpdatedOn), DateTime.Now);
        return (records, updateTarget);
    }

    internal static (List<FieldChangeRecord>, UpdateDefinition<TEntity>) UpdateWithValue<TEntity>(this TEntity source,
        IDictionary<string, object> data)
        where TEntity : class, IEntity
    {
        List<FieldChangeRecord> records = new();
        var props = typeof(TEntity).GetProperties(BindingFlags.Public | BindingFlags.Instance |
                                                  BindingFlags.DeclaredOnly);

        foreach (var key in data.Keys)
        {
            var prop = props.FirstOrDefault(a => a.Name == key);
            if (prop != null)
            {
                var oldValue = prop.GetValue(source);
                var newValue = data[key];
                if ((oldValue == null && newValue != null) || (oldValue != null && newValue == null) ||
                    oldValue?.Equals(newValue) == false)
                    records.Add(new FieldChangeRecord
                    {
                        Field = prop.Name,
                        OldValue = oldValue,
                        NewValue = newValue
                    });
            }
        }

        var updateTarget = Builders<TEntity>.Update.Set(a => a.Id, source.Id);
        foreach (var item in records) updateTarget = updateTarget.Set(item.Field, item.NewValue);
        updateTarget = updateTarget.Set(nameof(IEntity.UpdatedOn), DateTime.Now);
        return (records, updateTarget);
    }

    internal static (List<FieldChangeRecord>, UpdateDefinition<TEntity>) UpdateWithValue<TEntity>(this TEntity source,
        TEntity value)
        where TEntity : class, IEntity
    {
        List<FieldChangeRecord> records = new();
        var props = typeof(TEntity).GetProperties(BindingFlags.Public | BindingFlags.Instance |
                                                  BindingFlags.DeclaredOnly);

        foreach (var prop in props)
        {
            var oldValue = prop.GetValue(source);
            var newValue = prop.GetValue(value);
            if ((oldValue == null && newValue != null) || (oldValue != null && newValue == null) ||
                oldValue?.Equals(newValue) == false)
                records.Add(new FieldChangeRecord
                {
                    Field = prop.Name,
                    OldValue = oldValue,
                    NewValue = newValue
                });
        }

        var updateTarget = Builders<TEntity>.Update.Set(a => a.Id, source.Id);
        foreach (var item in records) updateTarget = updateTarget.Set(item.Field, item.NewValue);
        updateTarget = updateTarget.Set(nameof(IEntity.UpdatedOn), DateTime.Now);
        return (records, updateTarget);
    }
}